﻿#region Using Directives

using System;
using System.Collections.Generic;
using System.IO;
using Kongo.Filters;
using Kongo.Tags;

#endregion Using Directives


namespace Kongo
{
    /// <summary>
    /// Represents a document with inline statements describing how text should be replaced with values.
    /// </summary>
    public partial class Template
    {
        #region Fields

        private static readonly object syncLock = new object();
        private static Dictionary<string, ITagCreator> tagCreators = CreateDefaultTags();
        private static Dictionary<string, Filter> filters = CreateDefaultFilters();

        private List<Node> nodes;

        #endregion Fields


        #region Methods

        private static Dictionary<string, Filter> CreateDefaultFilters()
        {
            // Create the filters table and add the default tags
            Dictionary<string, Filter> filters = new Dictionary<string, Filter>();
            filters.Add("apnumber", new Filter(ApNumberFilter.Handler));
            filters.Add("intcomma", new Filter(IntCommaFilter.Handler));
            filters.Add("format", new Filter(FormatFilter.Handler));
            filters.Add("lower", new Filter(LowerFilter.Handler));
            filters.Add("upper", new Filter(UpperFilter.Handler));
            filters.Add("yesno", new Filter(YesNoFilter.Handler));

            return filters;
        }


        private static Dictionary<string, ITagCreator> CreateDefaultTags()
        {
            // Create the tag creators table and add the default tags
            Dictionary<string, ITagCreator> tagCreators = new Dictionary<string, ITagCreator>();
            tagCreators.Add("comment", new CommentTag.CommentTagCreator());
            tagCreators.Add("cycle", new CycleTag.CycleTagCreator());
            tagCreators.Add("debug", new DebugTag.DebugTagCreator());
            tagCreators.Add("else", new EndTag.EndTagCreator()); // TODO Change EndTag to MarkerTag
            tagCreators.Add("end", new EndTag.EndTagCreator());
            tagCreators.Add("endcomment", new EndTag.EndTagCreator());
            tagCreators.Add("endfor", new EndTag.EndTagCreator());
            tagCreators.Add("endif", new EndTag.EndTagCreator());
            tagCreators.Add("if", new IfTag.IfTagCreator());
            tagCreators.Add("for", new ForTag.ForTagCreator());
            tagCreators.Add("now", new NowTag.NowTagCreator());
            tagCreators.Add("templatetag", new TemplateTagTag.TemplateTagTagCreator());

            return tagCreators;
        }


        internal static Filter GetFilter(string key)
        {
            Filter value;
            if (filters.TryGetValue(key, out value))
                return value;

            return null;
        }


        internal static ITagCreator GetTagCreator(string key)
        {
            ITagCreator value;
            if (tagCreators.TryGetValue(key, out value))
                return value;

            return null;
        }


        /// <summary>
        /// Registers a <see cref="Filter"/> for the specified identifier.
        /// </summary>
        /// <param name="name">The identifier that the <see cref="Filter"/> services.</param>
        /// <param name="filter">The <see cref="Filter"/> that gets called to process a variable.</param>
        /// <returns><c>true</c> if registration is successful; otherwise, <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="name"/> is a null reference.
        /// -or-
        /// <paramref name="filter"/> is a null reference.
        /// </exception>
        /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
        public static bool RegisterFilter(string name, Filter filter)
        {
            Helpers.ThrowIfNullOrEmpty(name, "name");
            Helpers.ThrowIfNull(filter, "filter");

            // Make this thread-safe by locking
            lock (syncLock)
            {
                // See if the filter has already been registered
                if (filters.ContainsKey(name))
                    return false;

                // Add the filter
                filters.Add(name, filter);
                return true;
            }
        }


        /// <summary>
        /// Registers a <see cref="Tag"/> descendant for the specified identifier.
        /// </summary>
        /// <param name="name">The identifier that the <see cref="Tag"/> descendant services.</param>
        /// <param name="creator">The create method that gets called to create the <see cref="Tag"/> descendant.</param>
        /// <returns><c>true</c> if registration is successful; otherwise, <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="name"/> is a null reference.
        /// -or-
        /// <paramref name="creator"/> is a null reference.
        /// </exception>
        /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
        public static bool RegisterTag(string name, ITagCreator creator)
        {
            Helpers.ThrowIfNullOrEmpty(name, "name");
            Helpers.ThrowIfNull(creator, "creator");

            // Make this thread-safe by locking
            lock (syncLock)
            {
                // See if the tag has already been registered
                if (tagCreators.ContainsKey(name))
                    return false;

                // Add the tag
                tagCreators.Add(name, creator);
                return true;
            }
        }
        

        /// <summary>
        /// Outputs the template's contents using the specified <see cref="Context"/>.
        /// </summary>
        /// <param name="context">The variables used to process the template.</param>
        /// <returns>The processed template.</returns>
        public string Render(Context context)
        {
            // Render all the nodes
            using (StringWriter output = new StringWriter())
            {
                Render(context, output);
                return output.ToString();
            }
        }


        /// <summary>
        /// Outputs the template's contents using the specified <see cref="Context"/> to the specified <see cref="TextWriter"/> output stream.
        /// </summary>
        /// <param name="context">The <see cref="Context"/> containing the variables used to process the template.</param>
        /// <param name="output">The <see cref="TextWriter" /> to output the template to.</param>
        public void Render(Context context, TextWriter output)
        {
            foreach (Node node in nodes)
                node.Render(context, output);
        }

        #endregion Methods


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Template"/> class using the specified string.
        /// </summary>
        /// <param name="source">String representing the template text.</param>
        public Template(string source)
        {
            this.nodes = new List<Node>();

            // Build out the nodes
            Parser parser = new Parser(this, source ?? String.Empty);
            parser.HitMeBabyOneMoreTime();
        }

        #endregion Constructors
    }
}
